#!/bin/bash
# Copyright (c) 2023 Institute of Parallel And Distributed Systems (IPADS), Shanghai Jiao Tong University (SJTU)
# Licensed under the Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#     http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v2 for more details.

set -e

self=$0

defconfig_command="$self defconfig"
clean_command="$self clean"

cmake_build_dir="build"
cmake_cache_file="build/CMakeCache.txt"

cmake_script_dir="scripts/build/cmake"
cmake_init_cache_default="$cmake_script_dir/LoadConfigDefault.cmake"
cmake_init_cache_ask="$cmake_script_dir/LoadConfigAsk.cmake"
cmake_init_cache_abort="$cmake_script_dir/LoadConfigAbort.cmake"
cmake_init_cache_dump="$cmake_script_dir/DumpConfig.cmake"

config_file=".config"
defconfig_dir="scripts/build/defconfigs"

repo_tool_dir=".repo"
manifest_upstream="git@ipads.se.sjtu.edu.cn:staros/staros-manifests.git"
repo_url="https://mirrors.tuna.tsinghua.edu.cn/git/git-repo"
ci_manifest_name="ci.xml"
default_manifest_name="default.xml"
docker_cgroup='/chcore_gitlab_runner/'

RED='\033[0;31m'
BLUE='\033[0;34m'
GREEN='\033[0;32m'
ORANGE='\033[0;33m'
BOLD='\033[1m'
NONE='\033[0m'

_echo_info() {
    echo -e "${BOLD}$@${NONE}"
}

_echo_succ() {
    echo -e "${GREEN}${BOLD}$@${NONE}"
}

_echo_warn() {
    echo -e "${ORANGE}${BOLD}$@${NONE}"
}

_echo_err() {
    echo -e "${RED}${BOLD}$@${NONE}"
}

defconfig() {
    if [ -d $cmake_build_dir ]; then
        _echo_err "There exists a build directory, please run \`$clean_command\` first"
        exit 1
    fi

    if [ -z "$1" ]; then
        plat="raspi3"
    else
        plat="$1"
    fi

    _echo_info "Generating default config file for \`$plat\` platform..."
    cp $defconfig_dir/${plat}.config $config_file
    _config_default
    _sync_config_with_cache
    _echo_succ "Default config written to \`$config_file\` file."
}

_generate_manifest() {
    name=$1
    shift
    echo "$@"
    ./scripts/build/generate_manifest.py --name $name --dest $repo_tool_dir/manifests $@
}

_ci_init() {
    repo init -u $manifest_upstream --depth=1 --repo-url $repo_url
    _generate_manifest $ci_manifest_name $@
}

ci_sync() {
    if [ ! -d $repo_tool_dir ]; then
        _ci_init $@
    fi
    repo sync -c --no-clone-bundle --no-tags -j$(nproc --all) -m $ci_manifest_name  --fetch-submodules
}

_init() {
    repo init -u $manifest_upstream --repo-url $repo_url
}

sync() {
    if [ ! -d $repo_tool_dir ]; then
        _init
    fi
    if [ -z "$1" ]; then
        manifest_name=$default_manifest_name
    elif [[ $1 =~ ".*\.xml$" ]]; then
        manifest_name=$1
    else
        manifest_name="$1.xml"
    fi
    repo sync -j$(nproc --all) -m $manifest_name
}

_check_config_file() {
    if [ ! -f $config_file ]; then
        _echo_err "There is no \`.config\` file, please run \`$defconfig_command\` first"
        exit 1
    fi
}

_config_default() {
    _echo_info "Configuring CMake..."
    cmake -B $cmake_build_dir -C $cmake_init_cache_default
}

_config_ask() {
    _echo_info "Configuring CMake..."
    cmake -B $cmake_build_dir -C $cmake_init_cache_ask
}

_sync_config_with_cache() {
    cmake -N -B $cmake_build_dir -C $cmake_init_cache_dump >/dev/null
}

config() {
    _check_config_file
    _config_ask
    _sync_config_with_cache
    _echo_succ "Config syned to \`$config_file\` file."
}

menuconfig() {
    _check_config_file
    _config_default

    echo
    _echo_warn "Note: In the menu config view, press C to save, Q to quit."
    read -p "Now press Enter to continue..."

    ccmake -B $cmake_build_dir
    _sync_config_with_cache
    _echo_succ "Config saved to \`$config_file\` file."
}

rambuild() {
    _echo_info "Building ramdisk..."

    cmake --build $cmake_build_dir --target rambuild --parallel $(nproc)
    
    _echo_succ "Succeeded to rebuild ramdisk"
}

build() {
    _check_config_file
    _config_ask
    _sync_config_with_cache
    _echo_succ "Config syned to \`$config_file\` file."

    if [ -z "$1" ]; then
        build_target="all"
    else
        build_target="$1"
    fi

    _echo_info "Building..."
    cmake --build $cmake_build_dir --target $build_target --parallel $(nproc)

    if [ -z "$1" ]; then
        _echo_succ "Succeeded to build all targets"
        _echo_info "To use chcore toolchain, please input the commnad"
        _echo_info "\t export CMAKE_TOOLCHAIN_FILE=$PWD/build/toolchain.cmake"
    else
        _echo_succ "Succeeded to build \`$build_target\`"
    fi
}

clean() {
    _echo_info "Cleaning..."

    if [ -z "$1" ]; then
        if [ -f $cmake_cache_file ]; then
            _config_default
            cmake --build $cmake_build_dir --target clean-all
            rm -rf $cmake_build_dir
            _echo_succ "Succeeded to clean all targets"
        elif [ -d $cmake_build_dir ]; then
            rm -rf $cmake_build_dir
            _echo_warn "Succeeded to clean all targets, but some object files may be left"
        else
            _echo_info "Nothing to clean"
        fi
    else
        if [ -f $cmake_cache_file ]; then
            cmake --build $cmake_build_dir --target $1-clean
            _echo_succ "Succeeded to clean \`$1\`"
        else
            _echo_err "Cannot clean \`$1\`, please try \`$self clean\` to clean all"
        fi
    fi
}

distclean() {
    clean
    _echo_info "Removing config file..."
    rm -rf $config_file

    _echo_succ "Succeeded to distclean"
}

_print_help() {
    echo -e "\
${BOLD}USAGE:${NONE}

    ${BOLD}$self [options] [command]${NONE}

${BOLD}OPTIONS:${NONE}

    ${BOLD}--local, -l${NONE}           run command in local environment (rather than docker)

${BOLD}COMMANDS:${NONE}

  Local:

    ${BOLD}help, --help, -h${NONE}      print this help text
    ${BOLD}update-submodules${NONE}     update Git submodules according to config file

  Local or Docker:

    ${BOLD}defconfig [platform]${NONE}  generate default config (\`raspi3\` if \`platform\` is not specified)
    ${BOLD}config${NONE}                run configuration step (interactively ask for config value if not set)
    ${BOLD}menuconfig${NONE}            use TUI menu to edit config
    ${BOLD}build [target]${NONE}        build the project (\`target\` can be \`libc\`, \`user\`, \`kernel\` or empty)
    ${BOLD}clean [target]${NONE}        clean the project (\`target\` can be \`libc\`, \`user\`, \`kernel\` or empty)
    ${BOLD}distclean${NONE}             clean the project and remove config file
"
}

_update_submodules() {
    if [ -d $cmake_build_dir/local ]; then
        rm -rf $cmake_build_dir/local
    fi

    _echo_info "Configuring CMake in local environment..."
    cmake -B $cmake_build_dir/local -C $cmake_init_cache_abort

    _echo_info "Updating Git submodules..."
    cmake --build $cmake_build_dir/local --target update-submodules

    rm -rf $cmake_build_dir/local

    _echo_succ "Succeeded to update submodules"
}

_docker_run() {
    if [ -f /.dockerenv ]; then
        # we are already in docker container
        $@
    else
        use_cgroup=""
        if [[ $CI_RUNNER_TAGS =~ .*need_cgroup.* ]]; then
            use_cgroup="--cgroup-parent=$docker_cgroup"
        fi
        echo "chbuild: use_cgroup: $use_cgroup, tags: $CI_RUNNER_TAGS"
        test -t 1 && use_tty="-t"
        docker run -i $use_tty --rm \
            -u $(id -u ${USER}):$(id -g ${USER}) \
            -v $(pwd):$(pwd) -w $(pwd) \
            --security-opt=seccomp:unconfined \
            ipads/chcore_builder:v1.8.3 \
            $self $@
    fi
}

_main() {
    run_in_docker=true
    while [ $# -gt 0 ]; do
        case $1 in
        help | --help | -h)
            _print_help
            exit
            ;;
        --local | -l)
            run_in_docker=false
            ;;
        -*)
            _echo_err "$self: invalid option \`$1\`\n"
            break
            ;;
        update-submodules)
            # need Git SSH key authentication, so must run in local env
            _update_submodules
            exit
            ;;
        sync)
            # need Git SSH key authentication, so must run in local env
            shift
            sync $@
            exit
            ;;
        ci_sync)
            # need Git SSH key authentication, so must run in local env
            shift
            ci_sync $@
            exit
            ;;
        *)
            if [[ "$1" == "_"* || $(type -t "$1") != function ]]; then
                _echo_err "$self: invalid command \`$1\`\n"
                break
            fi

            if [[ $run_in_docker == true ]]; then
                _docker_run $@
            else
                $@
            fi
            exit
            ;;
        esac
        shift
    done

    # no command is run
    _print_help
    exit 1
}

_main $@
